package com.erp.common.web.pagination;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.erp.framework.model.criteria.SimpleBaseSrchCriteria;
import com.erp.framework.model.vo.SimpleBaseVo;
import com.erp.framework.pagination.PageControl;
import com.erp.framework.pagination.PageDataAccess;
import com.erp.framework.pagination.PageSearchCriteria;
import com.erp.framework.pagination.Pageable;
import com.erp.framework.util.logger.Logger;

public class PageController implements Serializable, PageControl {
	public static int PAGE_SIZE = 20;

	public static int MAX_PAGES_ALLOWED = 25;

	public static int PREFETCH_PAGES = 1;

	private int pageSize = PAGE_SIZE;

	private int maxPagesAllowed = MAX_PAGES_ALLOWED;

	private int prefetchPages = PREFETCH_PAGES;

	private int size;
	
	private int numberOfPages;

	private int currentPage = 0;

	private boolean allowRefetch = false;

	private List pages = null;
	
	private PageDataAccess pda = null;

	private SimpleBaseVo model = null;
	
	private SimpleBaseSrchCriteria criteria = null;
	
	private Class pageImpl = null;
	
	private String userCode;

	public PageController(PageDataAccess pda, SimpleBaseVo model, SimpleBaseSrchCriteria criteria) {
		this.pda = pda;
		this.model = model;
		this.criteria = criteria;
	}

	public Pageable getCurrentPage() {
		return getPage();
	}

	public int getMaxPagesAllowed() {
		return maxPagesAllowed;
	}

	public void setMaxPagesAllowed(int maxPagesAllowed) {
		this.maxPagesAllowed = maxPagesAllowed;
	}

	public List getPages() {
		return pages;
	}

	public void setPages(ArrayList pages) {
		this.pages = pages;
	}

	public int getPageSize() {
		return pageSize;
	}

	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}

	public int getPrefetchPages() {
		return prefetchPages;
	}

	public void setPrefetchPages(int prefetchPages) {
		this.prefetchPages = prefetchPages;
	}

	public int getTotalSize() {
		return size;
	}
	
	public int getLogicalPageNo() {
		return currentPage + 1;
	}
	
	public int getNumberOfPages() {
		return numberOfPages; 
	}
	
	public Class getPageImpl() {
		return pageImpl;
	}

	public void setPageImpl(Class pageImpl) {
		this.pageImpl = pageImpl;
	}

	public boolean hasNextPage() {
		return currentPage < Math.min(getNumberOfPages() - 1, maxPagesAllowed - 1);
	}

	public boolean hasPreviousPage() {
		return currentPage > 0;
	}

	public Pageable getNextPage() {
		if (hasNextPage())
			currentPage++;

		return getPage();
	}

	public Pageable getPreviousPage() {
		if (hasPreviousPage())
			currentPage--;
		else
			currentPage = 0;

		return getPage();
	}

	public Pageable getPage(int page_index) {
		int n_pages = getNumberOfPages();
		
		if (page_index < 0)
			page_index = 0;
		else if (page_index > n_pages - 1)
			page_index = n_pages - 1;
		
		currentPage = page_index;
		
		return getPage();
	}

	/**
	 * Get the Pageable specified by the currentPage variable
	 * 
	 * @return
	 */
	public Pageable getPage() {
		if (currentPage >= size)
			return null;
		
		Pageable page = (Pageable)pages.get(currentPage);
		
		if (page == null || !page.isFetched()) {
			refill(1);
			
			page = (Pageable)pages.get(currentPage);
		}
		
		return page;
	}
	
	private void refill(int refill_page) {
		if (refill_page <= 0)
			throw new PageAccessException("Invalid refill size: " + refill_page);
		
		Collection items = null;
		
		Pageable prev_page = null;
		
		if (currentPage > 0)
			prev_page = (Pageable)pages.get(currentPage - 1);
		
		try {
			if (criteria instanceof PageSearchCriteria && prev_page != null && prev_page.isFetched()) {
				List list = prev_page.getItems();
				Object obj = list.get(list.size() - 1);
				((PageSearchCriteria)criteria).setRecordBreaker(obj);
			}
				
			items = pda.getPage(model, criteria, this.pageSize * refill_page, currentPage * this.pageSize);
		} catch (Exception e) {
			Logger.logExceptions(this, e);
		}
		
		if (items == null)
			throw new PageAccessException("Items list is null");
		
		if (!(items instanceof List))
			throw new PageAccessException("The underlying resultset should be stored in List type");
		
		List list = (List)items;
		
		for (int i = 0; i < refill_page; i++) {
			Pageable page = (Pageable)pages.get(currentPage + i);
			page.putItems(list.subList(this.pageSize * i, Math.min(list.size(), this.pageSize * (i + 1))));
		}
	}

	public boolean isAllowRefetch() {
		return allowRefetch;
	}

	public void setAllowRefetch(boolean allowRefetch) {
		this.allowRefetch = allowRefetch;
	}

	public void execute() {		
		try {
			this.size = pda.getTotal(model, criteria);
			this.numberOfPages = (int)Math.ceil(size * 1d / pageSize);
		} catch (Exception e) {
			Logger.logExceptions(this, e);
			this.size = 0;
			this.numberOfPages = 0;
		}
		
		if (size > 0) {
			pages = new ArrayList(Math.min(this.maxPagesAllowed, this.numberOfPages));
			
			for (int i = 0; i < this.numberOfPages; i++) {
				Pageable page = null;
				
				if (this.pageImpl == null)
					page = new Page(i);
				else {
					try {
						page = (Pageable)this.pageImpl.newInstance();
						page.setPageIndex(i);
					} catch (InstantiationException e) {
						Logger.logExceptions(this, e);
					} catch (IllegalAccessException e) {
						Logger.logExceptions(this, e);
					}
				}
				
				pages.add(i, page);
			}
			
			refill(this.prefetchPages);
		} else 
			pages = new ArrayList(0);
	}

	public void release() {
		this.pages.clear();
		this.pda = null;
		this.model = null;
		this.criteria = null;
	}

	public int getPageNo() {
		return currentPage;
	}

	public int getLowerItemBound() {
		return currentPage * pageSize + 1;
	}

	public int getUpperItemBound() {
		return Math.min((currentPage + 1) * pageSize, size);
	}
	
	public void setUserCode(String code) {
		this.userCode = code;
	}
	
	public String getUserCode() {
		return this.userCode;
	}
}
